package xyz.heyaoshare.config.minio;

import io.minio.*;
import io.minio.errors.MinioException;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import xyz.heyaoshare.config.properties.MinioProperties;

import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import static xyz.heyaoshare.exceptions.code.ErrorInfo.MINIO_BUILD_POLICY_FAILED;
import static xyz.heyaoshare.exceptions.customize.ServerException.exception;


/**
 * Minio 服务
 * @author YueHe
 * @version 0.0.1
 * @since 2025/6/26 上午10:52
 */
@Slf4j
@Service
@ConditionalOnProperty(value = "project.configuration.minio.enable", havingValue = "true")
public class MinioService {

    public final String split = "/";

    @Resource
    private MinioClient minioClient;

    @Resource
    private MinioProperties minioProperties;

    /**
     * 检查存储桶是否存在
     *
     * @param bucketName 存储桶名称
     * @return boolean
     */
    @SneakyThrows
    public boolean bucketExists(String bucketName) {
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    /**
     * 创建存储桶
     *
     * @param bucketName 存储桶名称
     */
    @SneakyThrows
    public void makeBucket(String bucketName) {
        if (!bucketExists(bucketName)) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }

    /**
     * 创建一个存储桶并设置其策略。
     *
     * @param bucketName 存储桶名称
     * @param policyEnum 存储桶策略枚举
     * @throws RuntimeException 如果发生任何异常，则抛出运行时异常
     */
    @SneakyThrows
    public void makeBucket(String bucketName, BucketPolicyEnum policyEnum) {
        if (!bucketExists(bucketName)) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
        if (policyEnum != null) {
            setBucketPolicy(bucketName, policyEnum);
        }
    }

    /**
     * 列出所有存储桶
     *
     * @return List<Bucket>
     */
    @SneakyThrows
    public List<Bucket> listBuckets() {
        return minioClient.listBuckets();
    }

    /**
     * 删除存储桶
     *
     * @param bucketName 存储桶名称
     */
    @SneakyThrows
    public void removeBucket(String bucketName) {
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }

    /**
     * 上传文件
     *
     * @param file       文件
     * @param bucketName 存储桶名称
     * @param fileName   文件名称
     * @return 文件访问URL
     */
    @SneakyThrows
    public String upload(MultipartFile file, String bucketName, String fileName) {
        // 如果存储桶不存在则创建
        makeBucket(bucketName);

        // 上传文件
        minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build());

        // 返回文件访问URL`
        return getObjectUrl(bucketName, fileName);
    }

    /**
     * 上传文件
     *
     * @param file       文件
     * @param bucketName 存储桶名称
     * @return 文件访问URL
     */
    @SneakyThrows
    public String upload(MultipartFile file, String bucketName) {
        // 如果存储桶不存在则创建
        makeBucket(bucketName);

        // 生成唯一文件名
        String fileName = UUID.randomUUID() + "-" + file.getOriginalFilename();

        // 上传文件
        minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(file.getInputStream(), file.getSize(), -1).contentType(file.getContentType()).build());

        // 返回文件访问URL`
        return getObjectUrl(bucketName, fileName);
    }

    /**
     * 上传文件到默认存储桶
     *
     * @param file 文件
     * @return 文件访问URL
     */
    public String upload(MultipartFile file) {
        return upload(file, minioProperties.getBucketName());
    }

    /**
     * 下载文件
     *
     * @param bucketName 存储桶名称
     * @param objectName 文件名称
     * @return InputStream
     */
    @SneakyThrows
    public InputStream download(String bucketName, String objectName) {
        return minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }


    /**
     * 删除文件
     *
     * @param bucketName 存储桶名称
     * @param objectName 文件名称
     */
    @SneakyThrows
    public void removeObject(String bucketName, String objectName) {
        minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    /**
     * 获取文件信息
     *
     * @param bucketName 存储桶名称
     * @param objectName 文件名称
     * @return StatObjectResponse
     */
    @SneakyThrows
    public StatObjectResponse getObjectInfo(String bucketName, String objectName) {
        return minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }


    /**
     * 生成预签名的下载URL
     *
     * @param bucketName 存储桶名称
     * @param objectName 对象名称
     * @param expiryDays 预签名URL的有效期天数
     * @return 预签名的下载URL
     * @throws MinioException 如果生成预签名URL时发生错误，抛出MinioException异常
     */
    @SneakyThrows
    public String generateDownloadUrl(String bucketName, String objectName, int expiryDays) throws MinioException {
        return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(bucketName).object(objectName).expiry(expiryDays, TimeUnit.DAYS).build());
    }

    /**
     * 生成预签名的下载URL
     *
     * @param bucketName 存储桶名称
     * @param objectName 对象名称
     * @param expiry     有效期
     * @param timeUnit   时间单位
     * @return 返回生成的下载URL
     * @throws MinioException 如果发生异常则抛出 MinioException
     */
    @SneakyThrows
    public String generateDownloadUrl(String bucketName, String objectName, int expiry, TimeUnit timeUnit) throws
            MinioException {
        return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(bucketName).object(objectName).expiry(expiry, timeUnit).build());
    }


    /**
     * 获取对象存储中的文件的访问URL
     *
     * @param bucketName 存储桶名称
     * @param fileName   文件名称
     * @return 文件访问URL
     */
    private String getObjectUrl(String bucketName, String fileName) {
        String endpoint = minioProperties.getEndpoint();
        final String split = "/";
        endpoint = endpoint.replaceAll("/{2,}", "/");
        if (endpoint.length() > 1 && endpoint.endsWith(split)) {
            endpoint = endpoint.substring(0, endpoint.length() - 1);
        }

        bucketName = bucketName.replaceAll("/{2,}", "/");
        if (bucketName.startsWith(split)) {
            bucketName = bucketName.substring(1);
        }

        if (bucketName.length() > 1 && bucketName.endsWith(split)) {
            bucketName = bucketName.substring(0, bucketName.length() - 1);
        }

        fileName = fileName.replaceAll("/{2,}", "/");
        if (fileName.startsWith(split)) {
            fileName = fileName.substring(1);
        }

        return endpoint + split + bucketName + split + fileName;
    }

    /**
     * 设置桶策略
     *
     * @param bucketName 桶名称
     * @param policy     桶策略枚举
     * @throws xyz.heyaoshare.exceptions.customize.ServerException 如果设置桶策略失败，抛出异常
     */
    public void setBucketPolicy(String bucketName, BucketPolicyEnum policy) {
        try {
            minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(BucketPolicyEnum.policyMatch(policy, bucketName)).build());
        } catch (Exception e) {
            log.error("生成桶策略失败：", e);
            throw exception(MINIO_BUILD_POLICY_FAILED);
        }
    }

    /**
     * 获取对象名称
     *
     * @param folders 文件夹路径数组
     * @return 对象名称字符串
     */
    public String getObjectName(String... folders) {
        if (folders.length == 0) {
            log.error("folders can't be empty");
            throw new IllegalArgumentException("folders can't be empty");
        }

        StringBuilder path = new StringBuilder();
        for (String folder : folders) {
            folder = folder.replaceAll("/{2,}", "/");
            if (!folder.startsWith(split)) {
                folder = split + folder;
            }
            if (folder.length() > 1 && folder.endsWith(split)) {
                folder = folder.substring(0, folder.length() - 1);
            }
            path.append(folder);
        }
        return path.toString();
    }

    /**
     * 获取默认文件夹名称
     *
     * @return 默认文件夹名称，格式为"yyyy/MM/dd"
     */
    public String getDefaultFolder() {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        return split + simpleDateFormat.format(new Date());
    }

}
